package de.saarland.uni.lego3d.server.gui.handler;

import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import com.sun.j3d.utils.geometry.Primitive;
import com.sun.j3d.utils.picking.PickCanvas;
import com.sun.j3d.utils.picking.PickResult;

import de.saarland.uni.lego3d.Lego3D;
import de.saarland.uni.lego3d.server.KinectAction;
import de.saarland.uni.lego3d.server.Server;
import de.saarland.uni.lego3d.server.gui.LegoBrick3D;

/**
 * Mouse handler to treat interaction via mouse events.
 *
 */
public class BrickPanelMouseHandler implements MouseListener {

	/**
	 * Viewpoint of the given Panel.
	 * This flag should be used to take care of movements when current viewpoint has changed
	 * TODO: Implement the above mentioned behavior 
	 */
	private int viewpoint = -1;
	
	/**
	 * the selected lego brick
	 */
	private LegoBrick3D selectedBrick = null;
	
	/**
	 * Instance of the pickCanvas object of the selected subgraph
	 */
	private PickCanvas pickCanvas;
	
	/**
	 * thread that applies the movement in x direction
	 */
	private XMover xMover;
	
	/**
	 * thread that applies the movement in y direction
	 */
	private YMover yMover;
	
	/**
	 * thread that applies the movement in z direction
	 */
	private ZMover zMover;
	
	/**
	 * MouseHandler for global interaction with GUI
	 */
	public BrickPanelMouseHandler () {
		//Need empty constructor for Java3D internals
	}
	
	/**
	 * Mousehandler when added to one of the java3D interaction panels (canvases)
	 */
	public BrickPanelMouseHandler (int _viewpoint, PickCanvas _canvas) {
		viewpoint = _viewpoint;
		pickCanvas = _canvas;
	}
	
	@Override
	public void mouseClicked(MouseEvent e) {
		//nothing to do
	}

	@Override
	public void mouseEntered(MouseEvent e) {
		//nothing to do
	}

	@Override
	public void mouseExited(MouseEvent e) {
		//nothing to do
	}

	/**
	 * The mousePressed event is initiated by a kinect message, when brick modes get active
	 * and it is enabled as long as brick mode is active.
	 */
	@Override
	public void mousePressed(MouseEvent e) {
		pickCanvas.setShapeLocation(e);

		//get the result of the click event
	    PickResult result = pickCanvas.pickClosest();

	    if (result == null) {
	       if (Lego3D.DEBUG)
	    	   System.out.println("Nothing picked");
	       
	       selectedBrick = null;
	    } else {
	    	//get the primitive of the result
	       Primitive p = (Primitive)result.getNode(PickResult.GROUP);
	       if (p != null) {
	    	  try { 
	    		  //get the selected brick
	    		  selectedBrick = (LegoBrick3D) p.getParent().getParent();
	    	  } catch (Exception a) {
	    		  if (Lego3D.DEBUG)
	    			  System.out.println("No brick selected");
	    		  
				  selectedBrick = null;
	        	  return;
	          }
	          
	    	  //checks if the selected brick was already composed
	          try {
	        	  LegoBrick3D composedBrick;
	        	  composedBrick = (LegoBrick3D) selectedBrick.getParent().getParent().getParent();

	        	  while (true) {
	        		  if (composedBrick.composed) {
	        			  selectedBrick = composedBrick;
	        			  break;
	        		  }
	        		  //Go upward the subgraph until a parent is reached that defines a composed model
	        		  //or an Exception indicates, that there is no parent reachable anymore
	        		  composedBrick = (LegoBrick3D) composedBrick.getParent().getParent().getParent();
	        	  }
	          } catch (ClassCastException b) {
	        	  //nothing
	          } catch (NullPointerException c) {
	        	  //nothing
	          }
	          
	          if (Lego3D.DEBUG)
	        	  System.out.println(selectedBrick.getName() + " with depth: " + selectedBrick.brickDef.getHeighestDepth() + ", width: " + selectedBrick.brickDef.getLayout().length);
	          
	          //Apply the kinect action to the selected brick
	          if (Server.gui.current_kinectMsg != null && Server.gui.current_kinectMsg.isAction_brick()) {
		          xMover = new XMover(); xMover.start();
		          yMover = new YMover(); yMover.start();
		          zMover = new ZMover(); zMover.start();
	          } else if (Server.gui.current_kinectMsg != null && Server.gui.current_kinectMsg.isAction_mouse()) {
	        	  //Do stuff with mouse mode and click action
	        	  //TODO: Think of use-cases
	          }
	       } else {
	    	   if (Lego3D.DEBUG)
	    		   System.out.println("No brick selected");
	    	   
	    	   selectedBrick = null;
	       }
	    }
	}

	/**
	 * The mousePressed event is initiated by a kinect message, when brick mode switched to false
	 */
	@Override
	public void mouseReleased(MouseEvent e) {
		//interrupt and kill all threads handling the movement of the brick
		if (xMover != null) {
			xMover.interrupt();
			try {
				xMover.join();
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
			xMover = null;
		}
		
		if (yMover != null) {
			yMover.interrupt();
			try {
				yMover.join();
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
			yMover = null;
		}
		
		if (zMover != null) {
			zMover.interrupt();
			try {
				zMover.join();
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
			zMover = null;
		}
		
		selectedBrick = null;
		
		if (Lego3D.DEBUG)
			System.out.println("Released brick");
	}

	/**
	 * Thread handling the movement of the selected brick in x direction
	 */
	private class XMover extends Thread {
		@Override
		public void run() {
			
			while (!isInterrupted()) {
			
				//get the kinect message
				KinectAction msg = Server.gui.current_kinectMsg;
				if (msg != null) {
					int speed = 600; //fastest ... values from 100 to 600 possible and every 10 percent acceleration cause +50 of speed
					
					//check in which x-direction the brick has to be moved and apply the movement
					if (msg.getDirection_left() > 0) {
						speed -= (msg.getDirection_left() / 10) * 50;
						if (selectedBrick != null) {
							selectedBrick.move(-1, 0, 0);
						}
					} else if (msg.getDirection_right() > 0) {
						speed -= (msg.getDirection_right() / 10) * 50;
						if (selectedBrick != null) {
							selectedBrick.move(1, 0, 0);
						}
					}
					
					try {
						sleep(speed);
					} catch (InterruptedException e) {
						interrupt();
					}
				}
				
			}
		}
	}
	
	/**
	 * Thread handling the movement in y direction
	 */
	private class YMover extends Thread {
		@Override
		public void run() {
			
			while (!isInterrupted()) {
			
				//get the kinect message
				KinectAction msg = Server.gui.current_kinectMsg;
				if (msg != null) {
					int speed = 600; //fastest ... values from 100 to 600 possible and every 10 percent acceleration cause +50 of speed
					
					//check in which y-direction the brick has to be moved and apply the movement
					if (msg.getDirection_up() > 0) {
						speed -= (msg.getDirection_up() / 10) * 50;
						if (selectedBrick != null) {
							selectedBrick.move(0, 1, 0);
						}
					} else if (msg.getDirection_down() > 0) {
						speed -= (msg.getDirection_down() / 10) * 50;
						if (selectedBrick != null) {
							selectedBrick.move(0, -1, 0);
						}
					}
					
					try {
						sleep(speed);
					} catch (InterruptedException e) {
						interrupt();
					}
				}
				
			}
		}
	}
	
	/**
	 * Thread handling the movement in z direction
	 */
	private class ZMover extends Thread {
		@Override
		public void run() {
			
			while (!isInterrupted()) {
			
				//get the kinext message
				KinectAction msg = Server.gui.current_kinectMsg;
				if (msg != null) {
					int speed = 600; //fastest ... values from 100 to 600 possible and every 10 percent acceleration cause +50 of speed
					
					//check in which z-direction the brick has to be moved and apply the movement
					if (msg.getDirection_forward() > 0) {
						speed -= (msg.getDirection_forward() / 10) * 50;
						if (selectedBrick != null) {
							selectedBrick.move(0, 0, -1);
						}
					} else if (msg.getDirection_backward() > 0) {
						speed -= (msg.getDirection_backward() / 10) * 50;
						if (selectedBrick != null) {
							selectedBrick.move(0, 0, 1);
						}
					}
					
					try {
						sleep(speed);
					} catch (InterruptedException e) {
						interrupt();
					}
				}
				
			}
		}
	}
}
